<cfcomponent accessors="true">
	
	
	<cfproperty name="name" type="string" />
	<cfproperty name="path" type="string" />
	<cfproperty name="defaultKeyStartFormat" type="string" />
	<cfproperty name="defaultKeyEndFormat" type="string" />
	<cfproperty name="queue" type="array" />
	<cfproperty name="isInProcess" type="boolean" />
	<cfproperty name="smartUpdate" type="boolean" />	


	<cffunction name="init" access="public" returntype="any">
		<cfargument name="name" type="string" required="yes">
		<cfargument name="path" type="string" required="yes">
		<cfargument name="defaultKeyStartFormat" type="string" required="yes">
		<cfargument name="defaultKeyEndFormat" type="string" required="yes">
		<cfargument name="smartUpdate" type="boolean" required="no" default="true">
		<cfscript>
			setName(arguments.name);
			setPath(arguments.path);
			setDefaultKeyStartFormat(arguments.defaultKeyStartFormat);
			setDefaultKeyEndFormat(arguments.defaultKeyEndFormat);
			setIsInProcess(false);
			setQueue(ArrayNew(1));
			setSmartUpdate(arguments.smartUpdate);
			return this;
		</cfscript>
	</cffunction>
	
	
	<cffunction name="addSegment" access="public" returntype="any">
		<cfargument name="key" type="string" required="yes" />
		<cfargument name="content" type="string" required="no" default="" />
		<cfargument name="keyStartFormat" type="string" required="no" default="#getDefaultKeyStartFormat()#" />
		<cfargument name="keyEndFormat" type="string" required="no" default="#getDefaultKeyEndFormat()#" />
		<cfscript>		
			verifyFormat(
				key = arguments.key,
				startFormat = arguments.keyStartFormat,
				endFormat = arguments.keyendFormat
			);
			addSegmentActionToQueue(
				action = "ADD",
				key = arguments.key,
				content = arguments.content,
				keyStartFormat = arguments.keyStartFormat,
				keyEndFormat = arguments.keyEndFormat
			);
		</cfscript>
	</cffunction>
	
	
	<cffunction name="updateSegment" access="public" returntype="any">
		<cfargument name="key" type="string" required="yes" />
		<cfargument name="content" type="string" required="no" default="" />
		<cfargument name="keyStartFormat" type="string" required="no" default="#getDefaultKeyStartFormat()#" />
		<cfargument name="keyEndFormat" type="string" required="no" default="#getDefaultKeyEndFormat()#" />
		<cfscript>
			verifyFormat(
				key = arguments.key,
				startFormat = arguments.keyStartFormat,
				endFormat = arguments.keyendFormat
			);
			addSegmentActionToQueue(
				action = "UPDATE",
				key = arguments.key,
				content = arguments.content,
				keyStartFormat = arguments.keyStartFormat,
				keyEndFormat = arguments.keyEndFormat
			);
		</cfscript>
	</cffunction>
	
	
	<cffunction name="deleteSegment" access="public" returntype="any">
		<cfargument name="key" type="string" required="yes" />
		<cfargument name="content" type="string" required="no" default="" />
		<cfargument name="keyStartFormat" type="string" required="no" default="#getDefaultKeyStartFormat()#" />
		<cfargument name="keyEndFormat" type="string" required="no" default="#getDefaultKeyEndFormat()#" />
		<cfscript>
			verifyFormat(
				key = arguments.key,
				startFormat = arguments.keyStartFormat,
				endFormat = arguments.keyendFormat
			);
			addSegmentActionToQueue(
				action = "DELETE",
				key = arguments.key,
				content = arguments.content,
				keyStartFormat = arguments.keyStartFormat,
				keyEndFormat = arguments.keyEndFormat
			);
		</cfscript>
	</cffunction>
	
	
	
	<cffunction name="addSegmentActionToQueue" access="private" returntype="any">
		<cfargument name="action" type="string" required="yes" />
		<cfargument name="key" type="string" required="yes" />
		<cfargument name="content" type="string" required="no" default="" />
		<cfargument name="keyStartFormat" type="string" required="no" default="#getDefaultKeyStartFormat()#" />
		<cfargument name="keyEndFormat" type="string" required="no" default="#getDefaultKeyEndFormat()#" />
		<cfscript>
			local.oSegmentAction = CreateObject("component","FileSegmentManager.SegmentAction").init(
				action = arguments.action,
				key = arguments.key,
				content = arguments.content,
				keyStartFormat = arguments.keyStartFormat,
				keyEndFormat = arguments.keyEndFormat
			);
			ArrayAppend(getQueue(),local.oSegmentAction);
		</cfscript>
	</cffunction>
	
	
	<cffunction name="process" access="public" returntype="any">
		<cfscript>
			
			// Check to see if the process() method has already been called.
			if (getIsInProcess()) {
				return true;	
			}
			else {
				
				// set the in-process flag.
				setIsInProcess(true);
				
				// grab the queue. we want to make sure there's something to be
				// done in it before we make the IO effort to read and write to 
				// the file.
				local.queue = getQueue();
				if (NOT ArrayIsEmpty(local.queue)) {
					
					// Read the file in
					local.file = FileRead(getPath());
				
					// Loop through the rule queue
					for (local.i=1;local.i LTE ArrayLen(local.queue);local.i++) {
						
						// Grab the current action from queue
						local.sCurrentAction = local.queue[local.i].getAction();

						if (local.sCurrentAction EQ "ADD") {
							local.file = add(
								file = local.file,
								item = local.queue[i]
							);	
						}
						
						else if (local.sCurrentAction EQ "DELETE") {
							local.file = remove(
								file = local.file,
								item = local.queue[i]
							);
						}
						
						else if (local.sCurrentAction EQ "UPDATE") {
							local.file = update(
								file = local.file,
								item = local.queue[i]
							);
						}
						
						else {
							throw(
								type="UnknownItemAction",
								message="An item in the Writer queue has an unknown action property value."
							);
						}
					
					}
					
					// Write the changes to the file.
					FileWrite(getPath(), local.file);
					
				}

				// set out process flag to out of
				setIsInProcess(false);
			}
			
		</cfscript>
	</cffunction>
	
	
	<cffunction name="add" access="private" returntype="any">
		<cfargument name="file" type="string" />
		<cfargument name="item" type="FileSegmentManager.SegmentAction" />
		<cfscript>
			
			// Create a local copy of the file contents
			local.file = arguments.file;
			local.segmentText = formatSegmentText(
				startKey = arguments.item.getKeyStart(),	
				endKey = arguments.item.getKeyEnd(),
				segmentContent = arguments.item.getContent()
			);
			
			// First, determine if it already exists
			if (FindNoCase(arguments.item.getKeyStart(), arguments.file)) {
				if (getSmartUpdate()){
					local.file = update(arguments.file, arguments.item);
				}
				else {
					throw(
						type="SegmentAddFailedKeyConflict",
						message="A file segment could not be added the key #arguments.item.getKeyStart()# already exists."
					);	
				}
			}
			else {
				// Does not exist. Simply add.
				if (local.file NEQ "") { local.file = local.file & newline(2); }
				local.file = local.file & local.segmentText;
			}
			
			return local.file;
		</cfscript>
	</cffunction>
	
	
	<cffunction name="update" access="private" returntype="any">
		<cfargument name="file" type="string" />
		<cfargument name="item" type="FileSegmentManager.SegmentAction" />
		<cfscript>
			// Create a local copy of the file content.
			local.file = arguments.file;
			
			// Combine the start key, end key, and segment content into
			// a single block of text.
			local.segmentText = formatSegmentText(
				startKey = arguments.item.getKeyStart(),	
				endKey = arguments.item.getKeyEnd(),
				segmentContent = arguments.item.getContent()
			);
			
			// Look to see if we can find the start key in the existing file contents
			if (FindNoCase(arguments.item.getKeyStart(), arguments.file)) {
				// Start key found. Replace using a basic regular expression.
				local.file = REReplace(local.file,"#arguments.item.getKeyStart()#.*#arguments.item.getKeyEnd()#",local.segmentText);
			}
			else {
				// Did not find start key in the document.
				// Look to see if "smart update" is true or false
				if (NOT getSmartUpdate()){
					// Smart update was set to false, which means that when
					// attempting an update where a key is not found we throw an exception
					// rather than just fail silently.
					throw(
						type="SegmentUpdateFailedKeyNotPresent",
						message="A file segment could not be updated because the key #arguments.item.getKeyStart()# could not be found in the file contents."
					);	
				}
			}
			
			return local.file;
		</cfscript>
	</cffunction>
	
	
	
	<cffunction name="remove" access="private" returntype="any">
		<cfargument name="file" type="string" />
		<cfargument name="item" type="FileSegmentManager.SegmentAction" />
		<cfscript>
			// Create local copy of file contents
			local.file = arguments.file;
			
			// Format the rule text (combine start and end keys with the segment context)
			local.ruleText = formatSegmentText(
				startKey = arguments.item.getKeyStart(),	
				endKey = arguments.item.getKeyEnd(),
				segmentContent = arguments.item.getContent()
			);
			
			// First, confirm the segment already exists
			if (FindNoCase(arguments.item.getKeyStart(), arguments.file)) {
				// Already exists. Need to replace.
				local.file = REReplace(local.file,"#arguments.item.getKeyStart()#.*#arguments.item.getKeyEnd()#","");
			}
			
			return local.file;
		</cfscript>
	</cffunction>
	
	
	<cffunction name="formatSegmentText" access="private" returntype="string">
		<cfargument name="startKey" type="string" required="yes" />
		<cfargument name="segmentContent" type="string" required="yes" />
		<cfargument name="endKey" type="string" required="yes" />
		<cfreturn arguments.startKey & newline() & arguments.segmentContent & newline() & arguments.endKey />
	</cffunction>
	
	
	<cffunction name="verifyFormat" access="private" returntype="any">
		<cfargument name="key" type="string" required="yes" />
		<cfargument name="startFormat" type="string" required="yes" />
		<cfargument name="endFormat" type="string" required="yes" />
		
		<cfscript>
			
			if ((arguments.startFormat EQ "") OR (NOT FindNoCase("{key}",arguments.startFormat))) {
				throw(
					type="InvalidStartFormat",
					message="File '#getName()#' segment key '#arguments.key#' has an invalid starting format. It was either blank or did not contain a '{key}'. Found format: '#arguments.startFormat#'"
				);
			}
			
			if ((arguments.endFormat EQ "") OR (NOT FindNoCase("{key}",arguments.endFormat))) {
				throw(
					type="InvalidEndFormat",
					message="File '#getName()#' segment key '#arguments.key#' has an invalid end format. It was either blank or did not contain a '{key}'. Found format: '#arguments.endFormat#'"
				);
			}

		</cfscript>
		
	
	</cffunction>
	
	
	<cffunction name="newline" access="private" returntype="string">
		<cfargument name="numberOfLines" type="numeric" required="no" default="1" />
		<cfset local.newlineText = "" />
		<cfloop from="1" to="#arguments.numberOfLines#" index="i">
			<cfset local.newlineText = local.newlineText & chr(13) & chr(10) />
		</cfloop>
		<cfreturn local.newlineText />
	</cffunction>
	
</cfcomponent>